package com.uxsino.simo.collector.connections;

import java.io.IOException;
import java.util.Map;
import javax.net.ssl.SSLContext;

import org.apache.http.Header;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;
import com.uxsino.simo.connections.AbstractConnection;
import com.uxsino.simo.connections.exception.SimoConnectionException;
import com.uxsino.simo.connections.exception.SimoQueryException;
import com.uxsino.simo.connections.target.URLTarget;

public class URLConnection extends AbstractConnection<URLTarget> {

    private static Logger logger = LoggerFactory.getLogger(URLConnection.class);

    private int TIMEOUT_MS = 10000;

    private static int MAX_REDIRECT = 5;

    private CloseableHttpClient client;

    private String targetURL;

    private String requestType;

    @Override
    public int connect(URLTarget target) {
        super.connect(target);
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        if (target.getUsername() != null && target.getUsername().length() != 0) {
            credentialsProvider.setCredentials(AuthScope.ANY,
                new UsernamePasswordCredentials(target.getUsername(), target.getPassword()));
        }
        HttpClientBuilder builder = HttpClientBuilder.create();

		try {
            SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(null, (chain, authType) -> true)
                .build();
            builder.setSSLContext(sslContext);
        } catch (Exception e) {
            logger.error("error setting ssl context", e);
        }
        builder.setSSLHostnameVerifier((s, sslSession) -> true);

        builder.setDefaultCredentialsProvider(credentialsProvider);
        client = builder.build();
        connected = true;     // not really connected
        targetURL = target.getUrl();
        requestType = target.getRequestType().toUpperCase().trim();

        if (target.getTimeout() > 0) {
            TIMEOUT_MS = target.getTimeout();
        }
        state = 1;
        return state;
    }

    @Override
    public Object execCmd(Object cmdPattern) throws SimoQueryException, SimoConnectionException {
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(TIMEOUT_MS).setConnectTimeout(TIMEOUT_MS)
            .setConnectionRequestTimeout(TIMEOUT_MS).setRedirectsEnabled(true).setCircularRedirectsAllowed(false)
            .setRelativeRedirectsAllowed(true).setMaxRedirects(MAX_REDIRECT).build();
        HttpRequestBase request = null;
        switch (requestType) {
        case "GET":
            request = new HttpGet(targetURL);
            break;
        case "POST":
            request = new HttpPost(targetURL);
            break;
        case "DELETE":
            request = new HttpDelete(targetURL);
            break;
        case "HEAD":
            request = new HttpHead(targetURL);
            break;
        case "OPTIONS":
            request = new HttpOptions(targetURL);
            break;
        case "PUT":
            request = new HttpPut(targetURL);
            break;
        case "TRACE":
            request = new HttpTrace(targetURL);
            break;
        case "PATCH":
            request = new HttpPatch(targetURL);
            break;
        default:
            break;
        }
        if (request == null) {
            return null;
        }
        request.setConfig(requestConfig);
        CloseableHttpResponse response = null;
        try {
            long startTime = System.currentTimeMillis();
            response = client.execute(request);
            long endTime = System.currentTimeMillis();
            JSONObject result = new JSONObject();
            result.put("responseTime", endTime - startTime);
            result.put("requestMethod", requestType);
            result.put("requestURL", targetURL);
            result.put("statusCode", response.getStatusLine().getStatusCode());
            result.put("reasonPhrase", response.getStatusLine().getReasonPhrase());
            JSONObject headers = new JSONObject();
            result.put("headers", headers);
            Header[] hs = response.getAllHeaders();
            for (Header h : hs) {
                headers.put(h.getName(), h.getValue());
            }

            return result;
        } catch (IOException e) {
            logger.error("error getting http response ", e);
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    logger.error("failed to close http response", e);
                }
            }
            response = null;
        }

        return null;
    }

    @Override
    public Object buildCmd(String cmdPattern, Map<String, String> args) throws SimoQueryException {
        return cmdPattern;
    }

    @Override
    public int close() {
        if (client != null) {
            try {
                client.close();
            } catch (IOException e) {
                logger.error("failed to close client", e);
            } finally {
                client = null;
            }
        }
        connected = false;
        super.close();
        return 0;
    }

    @Override
    public boolean testWithConnected(String cmdString, String resStart) {
        try {
            Object res = execCmd("");
            if (res == null) {
                connected = false;
            }
        } catch (SimoQueryException | SimoConnectionException e) {
            connected = false;
            logger.error("connection test error ", e);
        }
        return connected;
    }

}
